// 时间复杂度和空间复杂度
//算法效率
//        算法效率分析分为两种：第一种是时间效率，第二种是空间效率。时间效率被称为时间复杂度，而空间效率被
//        称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度，而空间复杂度主要衡量一个算法所需要的额
//        外空间，在计算机发展的早期，计算机的存储容量很小。所以对空间复杂度很是在乎。但是经过计算机行业的
//        迅速发展，计算机的存储容量已经达到了很高的程度。所以我们如今已经不需要再特别关注一个算法的空间复
//        杂度。

//时间复杂度
//   时间复杂度的概念
//        时间复杂度的定义：在计算机科学中，算法的时间复杂度是一个函数，它定量描述了该算法的运行时间。一个
//        算法执行所耗费的时间，从理论上说，是不能算出来的，只有你把你的程序放在机器上跑起来，才能知道。但
//        是我们需要每个算法都上机测试吗？是可以都上机测试，但是这很麻烦，且不现实。所以才有了时间复杂度这个分析方
//        式。一个算法所花费的时间与其中语句的执行次数成正比例，算法中的基本操作的执行次数，为算法的时间复
//        杂度。


// 大O 的渐进表示法

// 让我们通过代码，来了解它
// 当我们看到这个代码时，首先，要找到运行次数最多的语句
//public class TimeComplexityAndSpaceComplexity {
//    public static  void func1(int N){
//        int count = 0;
//        for (int i = 0; i < N ; i++) {
//            for (int j = 0; j < N ; j++) {
//                count++;// 第一个就是 这个 count++;,它执行了多少次？
// //  两个for嵌套，两个for循环N次，最外围的for循环，每遍历一个数据，嵌套在内部的for循环，就要循环 N 次。
//                //即 count++; 语句 被循环执行 N*N 次，
//                // 小技巧，找执行次数最多的语句，你就看哪里有循环就行了。
//            }
//        }
//        for (int k = 0; k < 2 * N ; k++) {
//            count++;// 这里的 count++; 被执行了 2*N
//        }
//        int M = 10;
//        while ((M--) > 0) {
//            count++;// 这里count++; 被执行了 10次
//        }
//        System.out.println(count);
//    }
//}
//  经过我们分析，这句代码在程序中 一共被执行了 F(N) = N^2 + 2N + 10
// 在座的各位，跟着我思考一个问题：
// 如果我们是一个很大很大数字
// 比如：
//    N = 10 F(N) = 10^2 + 20 + 10 == 100 + 30 == 130
//    N = 100 F(N) = 100^2 + 200 + 10 == 10000 + 210 == 10210
//    N = 1000 F(N) = 1000^2 + 2000 + 10 == 1000000 + 2010 == 1002010
//     --- ---- ---
//    ---- ----  ---
// 按照这样想法，后面的 2N + 10，随着N增大，就显得微不足道。
//   在实际中我们计算时间复杂度时，我们其实并不一定要计算精确的执行次数，而只需要大概执行次数，那么这里
//        我们使用大O的渐进表示法。
//        大O符号（Big O notation）：是用于描述函数渐进行为的数学符号。
//  推导大O阶方法：
//        1、用常数1取代运行时间中的所有加法常数。{F(N)= N^2 +2N +1}
//        2、在修改后的运行次数函数中，只保留最高阶项。{F(N)=N^2}
//        3、如果最高阶项存在且不是1，则去除与这个项目相乘的常数。得到的结果就是大O阶
//   假设 使用完方法 1 和 2，把该做的都做了，还剩 3* N^2,
// 此时，按照方法3的规则去操作(去掉3*)，最终的时间复杂度为 O(N^2),
// 即分析这个代码的时间复杂度，在使用大O的渐进表示法以后
// Func1的时间复杂度为 O(N^2).


//通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项，简洁明了的表示出了执行次数。
//  另外有些算法的时间复杂度存在最好、平均和最坏情况：
//        最坏情况：任意输入规模的最大运行次数(上界)
//        平均情况：任意输入规模的期望运行次数（根据代码的情况给出平均时间复杂度）
//        最好情况：任意输入规模的最小运行次数(下界)
//        例如：在一个长度为N数组中搜索一个数据x
//        最好情况：1次找到
//        最坏情况：N次找到
//        平均情况：N/2次找到（可以这么理解，在中间的位置找到的。）

//在实际中一般情况关注的是算法的最坏运行情况，
// 举个例子：
// 假设数组中有N个元素，现在我们要在数组中查找一个元素，最坏的情况，该元素处在数组末尾，
// 也就是说 需要遍历整个数组元素。
// 故：数组中搜索数据时间复杂度为O(N)


// 再来看几个代码案例

// 案例1

//public class TimeComplexityAndSpaceComplexity {
//    public static  void func(int N){
//            int count = 0;
//            for (int k = 0; k < 2 * N ; k++) {
//                count++;// 2*N
//            }
//            int M = 10;
//            while ((M--) > 0) {
//                count++;// 10
//            }
//            System.out.println(count);
//        }
//    }
////  时间复杂度为 F(N) = 2*N + 10.使用大0渐进法来表示时间复杂度为 O(N)



// 案例 2
//public class TimeComplexityAndSpaceComplexity {
//    public static  void func(int N,int M) {
//        int count = 0;
//        for (int k = 0; k < M; k++) {
//            count++;// M
//        }
//        for (int k = 0; k < N; k++) {
//            count++;// N
//        }
//        System.out.println(count);
//    }
//}
//  时间复杂度为 F(N) = N + M.使用大0渐进法来表示时间复杂度为 O(N + M)



// 案例3
//public class TimeComplexityAndSpaceComplexity {
//    public static  void func(int N) {
//        int count = 0;
//        for (int k = 0; k < 100; k++) {
//            count++;// 100
//        }
//        System.out.println(count);
//    }
//}
//  时间复杂度为 F(N) = 100.使用大0渐进法来表示时间复杂度为 O(1)


// 大家在分析时间复杂度时，一定要结合思想，不能光看代码。

// 接下来，我们来分析 几个复杂 的 时间复杂度 的 程序

// 冒泡排序
//public class TimeComplexityAndSpaceComplexity {
//    public  void bubbleSort(int[] array) {
//        for (int end = array.length; end > 0; end--) {// 执行 length 次
//            boolean sorted = true;// 暂不考虑优化(数组已经是有序的, 也就是说 程序在遍历一次判断一下，就结束了)
//            // 最好情况：时间复杂度为 O(N)
//            for (int i = 1; i < end; i++) { //length -1 次
//                if (array[i -1]> array[i]) {
//                Swap(array, i - 1, i);
//                sorted = false;
//                }
//            }
//            if (sorted == true) {
//                break;
//            }
//        }
//
//    }
//}
// 时间复杂度是考虑最坏情况（每一个元素都需排序），不考虑优化情况和平均情况：
// 两个for循环嵌套length * (length - 1) == N*(N-1) = N^2 - N == N^2



// 二分查找
//public class TimeComplexityAndSpaceComplexity {
//    int binarySearch(int[] array, int value) {
//        int left = 0;
//        int right = array.length - 1;
//        while (left <= right) {
//            int mid = (right + left) / 2;
//            if (array[mid] < value) {
//                left = mid + 1;
//            } else if (array[mid] > value) {
//                right = mid - 1;
//            } else {
//                return mid;
//            }
//        }
//        return -1;
//    }
//}// 图1


// 递归
// 时间的复杂度  =  递归的次数 * 每次递归执行的次数
// 比如说：我这个递归，递归了N次（N）。每次下去（N） 就是 一个循环，循环是循环了 N 次
// 那么递归的时间的复杂度 为 N * N^2 = N^3

// 现在我们来看下面的程序（阶乘）
//public class TimeComplexityAndSpaceComplexity {
//    long factorial(int N) {
//        return N < 2 ? N : factorial(N-1) * N;
//    }
//}
// 虽然我们不知道该程序的递归次数，但是程序每次递归下去的时候，遇到是三目运算符。
// 三目运算符 不是循环，所以 每次递归的次数为1次
// 再来看看 程序，如果我们求的是 4 的阶乘，也就是 N==4
// N<2,那么只有 N==1时，返回 1（终止递归），然后在看后面 factorial(N-1)
// 每次递归减一，那么 4,3,2,1 一共 4次
// 也就是说  递归的次数 为 N 次，
// 所以   时间的复杂度  =  递归的次数 * 每次递归执行的次数 == N * 1 == N
// 即 O(N)


// 计算斐波那契递归fibonacci的时间复杂度？
//public class TimeComplexityAndSpaceComplexity {
//    int fibonacci(int N) {
//        return N < 2 ? N : fibonacci(N-1)+fibonacci(N-2);
//    }
//}
// 还是一样，每次进来就是一个 三目运算符
// 再来看 递归的次数
// 假设我们想求第四个斐波那契数，那么就需要我们去计算 第3 和 第4 个斐波那契数
// 因为斐波那契数，从第三位数开始，该数 等于 自身前两个数之和。

// 那么 第3个斐波那契数 ，需要 第1和第2个斐波那契数
// 第2个斐波那契数 需要 第1个斐波那契数，（因为N<2,是终止条件）
// 至于，第1个斐波那契数，就不需要再计算了（终止了）

// 第4个斐波那契数，需要 第3 和 第2个斐波那契数
// 第3个斐波那契数需要 第2 和 第1 个斐波那契数，第2个斐波那契数，需要第1个斐波那契数，（因为N<2,是终止条件）
// 至于，第2个斐波那契数，需要第一个斐波那契数 （因为N<2,是终止条件）

// 见 图 2

// 斐波那契数的 时间复杂度 见图3



//  空间复杂度
//  空间复杂度是对一个算法在运行过程中 临时 占用存储空间大小的量度 。空间复杂度不是程序占用了多少bytes的空间
//  因为这个也没太大意义，所以空间复杂度算的是变量的个数。
//  空间复杂度计算规则基本跟实践复杂度，类似，也使用大O渐进表示法。

// 冒泡排序 的 空间复杂度
//public class TimeComplexityAndSpaceComplexity {
//    public void bubbleSort(int[] array) {
//        for (int end = array.length; end > 0; end--) {
//            boolean sorted = true;
//            for (int i = 1; i < end; i++) {
//                if (array[i - 1] > array[i]) {
//                    Swap(array, i - 1, i);
//                    sorted = false;
//                }
//            }
//            if (sorted == true) {
//                break;
//            }
//        }
//    }
//}// 冒泡排序的整个运行的过程，没有说随着问题的规模，我们定义的变量也在增多。
// 也就说 冒泡排序的空间复杂度为 O(1)
// 有的人可能会说 sorted 在每次循环的时候，都会创建。
// 但是请注意 sorted 变量只有1个，你不能说我们循环10次，定义了10个 sorted
// 每次循环结束 变量空间是会被回收的，循环开始再创建一个。
// 也就是说 sorted 自始至终都是一个。
// 至于数组，不算。注意空间复杂度解释的题干中的 “临时” ，你可以理解为另外消耗的,或者说括号里的变量
// 这么说吧，数组是冒泡排序必须品，而 sorted 只是临时创建的，为了辅助这个程序的运行
// 所以”临时“的变量个数，只有sorted一个
// 故 该冒泡排序的空间复杂度为 O(1)


// 计算fibonacci的空间复杂度
//public class TimeComplexityAndSpaceComplexity {
//    int[] fibonacci(int n) {// 计算斐波那契数的第N项
//        long[] fibArray = new long[n + 1];// n如果越大，数据就越大，你的结果要存入这个数组里(这里的n+1，是因为下标，自己品)
//        // 下面的数组元素，最后都是要存入数组的，也就是说 下面生成的临时变量，都是存入数组的
//        // 即元素个数，就是 临时变量的个数，即求斐波那契数 的 空间复杂度 为 O(N+1)
//        // 再根据大O渐进法，空间复杂度的最终结果： O(N)
//        fibArray[0] = 0;
//        fibArray[1] = 1;
//        for (int i = 2; i <= n ; i++) {
//            fibArray[i] = fibArray[i - 1] + fibArray [i - 2];
//        }
//        return fibArray;
//    }
//}

// 计算阶乘递归Factorial的时间复杂度？
//public class TimeComplexityAndSpaceComplexity {
//    long factorial(int N) {
//        return N < 2 ? N : factorial(N-1)*N;
//    }
//}
// 递归的空间复杂度有点不一样。
// 每递归一次，函数就要在栈上开辟一块内存
// 也就是说递归一次，空间复杂度为 O(1)
// 递归N次，空间复杂度为 O(N)
// 即 阶乘递归函数的 空间复杂度为 O(N)












